// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef TRACE_READER_READER_INTERNAL_H_
#define TRACE_READER_READER_INTERNAL_H_

#include <fbl/string.h>
#include <fbl/unique_ptr.h>
#include <trace-engine/buffer_internal.h>
#include <trace-reader/reader.h>
#include <zircon/assert.h>

namespace trace {
namespace internal {

// Trace buffers have a header and one to three sub-buffers.
// This class provides an API for interpreting the header.
// See trace-engine/buffer.h for details.
class BufferHeaderReader {
 public:
  // Create a reader for the header at |header|.
  // The memory object containing |header| must survive this object.
  // |buffer_size| is the size of the full trace buffer, and is used to
  // validate the contents of the header.
  // Returns "" on success or an error message.
  // |header| must be suitably aligned to point to a header.
  static fbl::String Create(const void* header, size_t buffer_size,
                            fbl::unique_ptr<BufferHeaderReader>* out_reader);

  static int GetBufferNumber(uint32_t wrapped_count) {
    static_assert(fbl::count_of(trace_buffer_header{}.rolling_data_end) == 2, "");
    return wrapped_count & 1;
  }

  trace_buffering_mode_t buffering_mode() const {
    return static_cast<trace_buffering_mode_t>(header_->buffering_mode);
  }

  uint32_t wrapped_count() const { return header_->wrapped_count; }

  uint64_t total_size() const { return header_->total_size; }

  uint64_t durable_buffer_size() const { return header_->durable_buffer_size; }

  uint64_t rolling_buffer_size() const { return header_->rolling_buffer_size; }

  uint64_t durable_data_end() const { return header_->durable_data_end; }

  uint64_t rolling_data_end(int buffer_number) const {
    // static_assert(fbl::count_of(header_->rolling_data_end) == 2, "");
    ZX_DEBUG_ASSERT(buffer_number >= 0 && buffer_number <= 1);
    return header_->rolling_data_end[buffer_number];
  }

  uint64_t num_records_dropped() const { return header_->num_records_dropped; }

  // Return the offset of the durable buffer.
  uint64_t get_durable_buffer_offset() const { return sizeof(trace_buffer_header); }

  // Given a pointer to a trace buffer, return a pointer to the durable
  // buffer contained therein.
  const void* GetDurableBuffer(const void* buffer) const {
    auto buf = reinterpret_cast<const uint8_t*>(buffer);
    return buf + get_durable_buffer_offset();
  }

  // Return the offset of rolling buffer |buffer_number|.
  uint64_t GetRollingBufferOffset(int buffer_number) const {
    // static_assert(fbl::count_of(header_->rolling_data_end) == 2, "");
    ZX_DEBUG_ASSERT(buffer_number >= 0 && buffer_number <= 1);
    auto offset = sizeof(trace_buffer_header) + durable_buffer_size();
    if (buffer_number == 1) {
      offset += rolling_buffer_size();
    }
    return offset;
  }

  // Given a pointer to a trace buffer and a rolling buffer number,
  // return a pointer to the rolling buffer contained therein.
  const void* GetRollingBuffer(const void* buffer, int buffer_number) const {
    auto buf = reinterpret_cast<const uint8_t*>(buffer);
    return buf + GetRollingBufferOffset(buffer_number);
  }

  // These are temporary to allow a soft-roll of streaming support into
  // garnet. Delete after garnet side lands.
  uint64_t nondurable_buffer_size() const { return rolling_buffer_size(); }
  uint64_t nondurable_data_end(int buffer_number) const { return rolling_data_end(buffer_number); }
  uint64_t GetNondurableBufferOffset(int buffer_number) const {
    return GetRollingBufferOffset(buffer_number);
  }
  const void* GetNondurableBuffer(const void* buffer, int buffer_number) const {
    return GetRollingBuffer(buffer, buffer_number);
  }
  // End of temporary soft-roll changes.

 private:
  explicit BufferHeaderReader(const trace_buffer_header* header);

  static fbl::String Validate(const trace_buffer_header& header, size_t buffer_size);

  const trace_buffer_header* const header_;

  BufferHeaderReader(const BufferHeaderReader&) = delete;
  BufferHeaderReader(BufferHeaderReader&&) = delete;
  BufferHeaderReader& operator=(const BufferHeaderReader&) = delete;
  BufferHeaderReader& operator=(BufferHeaderReader&&) = delete;
};

// Reads a trace buffer a chunk at a time, where the buffer has a trace
// buffer header and subsequent contents.
// |chunk_consumer| is invoked for each chunk in the buffer.
class TraceBufferReader {
 public:
  // Called once for each chunk read by |ReadChunks|.
  using ChunkConsumer = fbl::Function<void(Chunk)>;

  // Callback invoked when an error is detected.
  using ErrorHandler = fbl::Function<void(fbl::String)>;

  TraceBufferReader(ChunkConsumer chunk_consumer, ErrorHandler error_handler);
  ~TraceBufferReader() = default;

  // Reads as many chunks as possible from the buffer, invoking the chunk
  // consumer for each (non-empty) one.
  // |buffer| must be suitably aligned to point to a trace buffer header.
  // Returns true on success, false if the buffer header is malformed.
  bool ReadChunks(const void* buffer, size_t buffer_size);

 private:
  void CallChunkConsumerIfNonEmpty(const void* chunk, size_t size);

  fbl::unique_ptr<BufferHeaderReader> const header_;
  ChunkConsumer chunk_consumer_;
  ErrorHandler error_handler_;

  TraceBufferReader(const TraceBufferReader&) = delete;
  TraceBufferReader(TraceBufferReader&&) = delete;
  TraceBufferReader& operator=(const TraceBufferReader&) = delete;
  TraceBufferReader& operator=(TraceBufferReader&&) = delete;
};

}  // namespace internal
}  // namespace trace

#endif  // TRACE_READER_READER_INTERNAL_H_
